/**********************************************************************
 * Filename: Voo.java
 * Created: 2013/05/29
 * @author Fernando Brito e Abreu
 **********************************************************************/
package org.quasar.airNova.businessLayer;

import java.util.LinkedList;
import java.util.Set;
import java.util.HashSet;
import org.quasar.airNova.persistenceLayer.Database;
import org.quasar.airNova.presentationLayer.Tools;

public class Voo {

	/***********************************************************
	 * @return all instances of class Voo
	 ***********************************************************/
	public static Set<Voo> allInstances() {
		return Database.allInstances(Voo.class);
	}

	private Instant hora_chegada;
	private Instant hora_partida;
	private int milhas;
	private String numero;
	private Aeroporto chegada;
	private Aeroporto partida;

	/**********************************************************************
	 * Default constructor
	 **********************************************************************/
	public Voo() {
	}

	/**********************************************************************
	 * Parameterized constructor
	 * 
	 * @param hora_chegada
	 *            the hora_chegada to initialize
	 * @param hora_partida
	 *            the hora_partida to initialize
	 * @param milhas
	 *            the milhas to initialize
	 * @param numero
	 *            the numero to initialize
	 * @param chegada
	 *            the chegada to initialize
	 * @param partida
	 *            the partida to initialize
	 **********************************************************************/
	public Voo(Instant hora_chegada, Instant hora_partida, int milhas,
			String numero, Aeroporto chegada, Aeroporto partida) {
		this.hora_chegada = hora_chegada;
		this.hora_partida = hora_partida;
		this.milhas = milhas;
		this.numero = numero;
		this.chegada = chegada;
		this.partida = partida;
	}

	/**********************************************************************
	 * Standard attribute getter
	 * 
	 * @return the hora_chegada of the voo
	 **********************************************************************/
	public Instant hora_chegada() {
		return hora_chegada;
	}

	/**********************************************************************
	 * Standard attribute setter
	 * 
	 * @param hora_chegada
	 *            the hora_chegada to set
	 **********************************************************************/
	public void setHora_chegada(Instant hora_chegada) {
		this.hora_chegada = hora_chegada;
	}

	/**********************************************************************
	 * Standard attribute getter
	 * 
	 * @return the hora_partida of the voo
	 **********************************************************************/
	public Instant hora_partida() {
		return hora_partida;
	}

	/**********************************************************************
	 * Standard attribute setter
	 * 
	 * @param hora_partida
	 *            the hora_partida to set
	 **********************************************************************/
	public void setHora_partida(Instant hora_partida) {
		this.hora_partida = hora_partida;
	}

	/**********************************************************************
	 * Standard attribute getter
	 * 
	 * @return the milhas of the voo
	 **********************************************************************/
	public int milhas() {
		return milhas;
	}

	/**********************************************************************
	 * Standard attribute setter
	 * 
	 * @param milhas
	 *            the milhas to set
	 **********************************************************************/
	public void setMilhas(int milhas) {
		this.milhas = milhas;
	}

	/**********************************************************************
	 * Standard attribute getter
	 * 
	 * @return the numero of the voo
	 **********************************************************************/
	public String numero() {
		return numero;
	}

	/**********************************************************************
	 * Standard attribute setter
	 * 
	 * @param numero
	 *            the numero to set
	 **********************************************************************/
	public void setNumero(String numero) {
		this.numero = numero;
	}

	/**********************************************************************
	 * ONE2MANY getter for Voo[*] <-> Aeroporto[1]
	 * 
	 * @return the chegada of the voo
	 **********************************************************************/
	public Aeroporto chegada() {
		return chegada;
	}

	/**********************************************************************
	 * ONE2MANY setter for Voo[*] <-> Aeroporto[1]
	 * 
	 * @param chegada
	 *            the chegada to set
	 **********************************************************************/
	public void setChegada(Aeroporto chegada) {
		this.chegada = chegada;
	}

	/**********************************************************************
	 * ONE2MANY getter for Voo[*] <-> Aeroporto[1]
	 * 
	 * @return the partida of the voo
	 **********************************************************************/
	public Aeroporto partida() {
		return partida;
	}

	/**********************************************************************
	 * ONE2MANY setter for Voo[*] <-> Aeroporto[1]
	 * 
	 * @param partida
	 *            the partida to set
	 **********************************************************************/
	public void setPartida(Aeroporto partida) {
		this.partida = partida;
	}

	/**********************************************************************
	 * MEMBER2ASSOCIATIVE getter for Voo[*] <-> Realizacao[*]
	 * 
	 * @return the realizacao of the realiza_se
	 **********************************************************************/
	public Set<Realizacao> realizacao() {
		Set<Realizacao> result = new HashSet<Realizacao>();
		for (Realizacao x : Realizacao.allInstances())
			if (x.realiza_se().numero().equals(this.numero()))
				result.add(x);
		return result;
	}

	/**********************************************************************
	 * MEMBER2ASSOCIATIVE setter for Voo[*] <-> Realizacao[*]
	 * 
	 * @param realizacao
	 *            the realizacao to set
	 **********************************************************************/
	public void setRealizacao(Set<Realizacao> realizacao) {
		for (Realizacao x : realizacao)
			x.setRealiza_se(this);
	}

	/**********************************************************************
	 * MEMBER2MEMBER getter for Voo[*] <-> CalendarDate[*]
	 * 
	 * @return the ocorre_em of the realiza_se
	 **********************************************************************/
	public Set<CalendarDate> ocorre_em() {
		Set<CalendarDate> result = new HashSet<CalendarDate>();
		for (Realizacao x : Realizacao.allInstances())
			if (x.realiza_se() == this)
				result.add(x.ocorre_em());
		return result;
	}

	/**********************************************************************
	 * MEMBER2MEMBER setter for Voo[*] <-> CalendarDate[*]
	 * 
	 * @param ocorre_em
	 *            the ocorre_em to set
	 **********************************************************************/
	public void setOcorre_em(Set<CalendarDate> ocorre_em) {
		for (CalendarDate t : ocorre_em)
			for (Realizacao x : Realizacao.allInstances())
				if (x.realiza_se() == this)
					x.setOcorre_em(t);
	}

	/**********************************************************************
	 * User-defined operation specified in SOIL/OCL
	 * 
	 * @param b
	 *            the b to set
	 **********************************************************************/
	public void ApagarBilheteDoVoo(Bilhete b) {
		b.ApagarBilhete();
		Database.close();
	}

	/**********************************************************************
	 * User-defined operation specified in SOIL/OCL
	 * 
	 * @param cd
	 *            the cd to set
	 **********************************************************************/
	public void CancelarVoo(CalendarDate cd) {
		Realizacao r = Tools.getRealizacao(this, cd);
		if (CancelarVooRealizacaoJaOcorrida(r)) {
			for (Bilhete each : r.bilhetes()) {
				each.ApagarBilhete();
			}
		}
		Database.delete(r);
		Database.close();
	}

	// PRE-CONDITIONS (TODO)
	/*
	 * pre CancelarVooRealizacaoJaOcorrida (Realizacao.allInstances->reject(r2 :
	 * Realizacao | (not r2.ocorre_em.isEqual(cd) and (r2.realiza_se <>
	 * self)))->asSequence->first.ocorre_em.isAfter(cd.today()) and not
	 * Realizacao.allInstances->reject(r2 : Realizacao | (not
	 * r2.ocorre_em.isEqual(cd) and (r2.realiza_se <>
	 * self)))->asSequence->first.ocorre_em.isEqual(cd.today()))
	 */

	// POST-CONDITIONS (TODO)
	/*
	 * post VooSemBilhetes Realizacao.allInstances->reject(r : Realizacao | (not
	 * r.ocorre_em.isEqual(cd) and (r.realiza_se <>
	 * self)))->asSequence->first.bilhetes->isEmpty
	 * 
	 * post CancelarVooCampoTrue (self.ocorre_em->collectNested($e :
	 * CalendarDate | $e.realizacao)->flatten->asSequence->first.cancelada =
	 * true)
	 */

	private boolean CancelarVooRealizacaoJaOcorrida(Realizacao r) {
		CalendarDate today = Tools.getToday();
		return r.ocorre_em().isAfter(today);
	}

	/**********************************************************************
	 * User-defined operation specified in SOIL/OCL
	 * 
	 * @param inicio
	 *            the inicio to set
	 * @param fim
	 *            the fim to set
	 **********************************************************************/
	public double OcupacaoMedia(CalendarDate inicio, CalendarDate fim) {
		// TODO
		// return let realizacoes_voo : Set(Realizacao) =
		// self.realizacao->reject($elem0 : Realizacao |
		// $elem0.cancelada)->select($elem1 : Realizacao | ((($elem1.ocorre_em =
		// inicio) or ($elem1.ocorre_em = fim)) or
		// ($elem1.ocorre_em.isAfter(inicio) and
		// $elem1.ocorre_em.isBefore(fim)))) in
		// (realizacoes_voo->collectNested($e : Realizacao |
		// $e.bilhetes)->flatten->size / realizacoes_voo->collectNested($e :
		// Realizacao | $e.aviao)->collectNested($e : Aviao |
		// $e.Lotacao())->sum)
		return -1;
	}

	/**********************************************************************
	 * User-defined operation specified in SOIL/OCL
	 * 
	 * @param c
	 *            the c to set
	 * @param a
	 *            the a to set
	 **********************************************************************/
	public void TrocarAviaoDoVoo(CalendarDate c, Aviao a) {
		if (preCondicoesTrocarAviaoDoVoo(this, c, a)) {
			Realizacao r = Tools.getRealizacao(this, c);
			generateToString(r, r.aviao());
			r.setAviao(a);
			generateToString(r, r.aviao());
		}

	}

	// PRE-CONDITIONS (TODO)
	/*
	 * pre TrocarAviaoIguais (Realizacao.allInstances->reject(r : Realizacao |
	 * (not r.ocorre_em.isEqual(c) and (r.realiza_se <>
	 * self)))->asSequence->first.aviao <> a)
	 * 
	 * pre TrocarAviaoLotacaoLugares Realizacao.allInstances->reject(r :
	 * Realizacao | (not r.ocorre_em.isEqual(c) and (r.realiza_se <>
	 * self)))->asSequence->first.aviao.classe->forAll(c1 : Classe |
	 * c1.bilhetes->forAll(b : Bilhete | ((b.lugar >= a.classe->select(cA :
	 * Classe | (cA = c1))->collectNested($e : Classe |
	 * $e.capacidade)->flatten->collectNested($e : Capacidade |
	 * $e.primeiro_numero)->asSequence->first) and (b.lugar <=
	 * a.classe->select(cA : Classe | (cA = c1))->collectNested($e : Classe |
	 * $e.capacidade)->flatten->collectNested($e : Capacidade |
	 * $e.ultimo_numero)->asSequence->first))))
	 */

	// POST-CONDITIONS (TODO)
	/*
	 * post TrocarAviaoAviaoNaoTrocado (Realizacao.allInstances->reject(r :
	 * Realizacao | (not r.ocorre_em.isEqual(c) and (r.realiza_se <>
	 * self)))->asSequence->first.aviao = a)
	 */

	private void generateToString(Realizacao r, Aviao aviao) {
		System.out.println("--");
		System.out.println("Aviao: " + aviao.nome());
		System.out.println("--");
		for (Capacidade cap: aviao.capacidade()) {
			System.out.println("Capacidade: " + cap.toString());
			System.out.println("Classe: " + cap.classe().toString());
			System.out.println("--- Bilhetes ---");
			for (Bilhete bilhete : r.bilhetes()) {
				if(bilhete.classe() == cap.classe()){
					System.out.println(bilhete.toString());
				}
			}
		}
		
	}

	private boolean preCondicoesTrocarAviaoDoVoo(Voo voo, CalendarDate c,
			Aviao a) {
		System.out.println("PRECONDICOES -> " + trocarAviaoIguais(voo, c, a));
		System.out.println("PRECONDICOES -> " + trocarAviaoLotacaoLugares(voo, c, a));
		return trocarAviaoIguais(voo, c, a)
				&& trocarAviaoLotacaoLugares(voo, c, a);
	}

	private boolean trocarAviaoLotacaoLugares(Voo v, CalendarDate c, Aviao aN) {
		Realizacao r = Tools.getRealizacao(v, c);
		Set<Bilhete> bilhetes = r.bilhetes();
		Set<Capacidade> capacidadeAv = aN.capacidade();
		Set<Classe> classe = new HashSet<>();
		for (Capacidade each : capacidadeAv) {
			classe.add(each.classe());
		}

		if (!checkClasses(bilhetes, classe))
			return false;

		return verifyCapacity(bilhetes, capacidadeAv);
	}

	private boolean verifyCapacity(Set<Bilhete> bilhetes,
			Set<Capacidade> capacidadeAv) {
		Capacidade capacidadeBilhete;
		for (Bilhete bilhete : bilhetes) {
			for (Capacidade each : capacidadeAv) {
				if (each.classe() == bilhete.classe()) {
					capacidadeBilhete = each;
					return capacidadeBilhete.primeiro_numero() <= bilhete
							.lugar()
							&& capacidadeBilhete.ultimo_numero() >= bilhete
									.lugar();
				} 
				else
					return false;
			}
		}
		return true;
	}

	private boolean checkClasses(Set<Bilhete> bilhetes, Set<Classe> classe) {
		for (Bilhete each : bilhetes) {
			if (!classe.contains(each.classe())) {
				return false;
			}
		}
		return true;
	}

	private boolean trocarAviaoIguais(Voo voo, CalendarDate c, Aviao a) {
		return !Tools.getRealizacao(voo, c).aviao().equals(a);
	}

	/**********************************************************************
	 * Object serializer
	 **********************************************************************/
	public String toString() {
		return "Voo [hora_chegada=" + hora_chegada + ", hora_partida="
				+ hora_partida + ", milhas=" + milhas + ", numero=" + numero
				+ ", chegada=" + chegada + ", partida=" + partida + "]";
	}

	// -------------------------------------------------------------------------------
	// INVARIANTS (TODO)
	/*
	 * inv vooHora_PartidaMenorQueHora_Chegada if (self.hora_partida.hours =
	 * self.hora_chegada.hours) then if (self.hora_partida.minutes =
	 * self.hora_chegada.minutes) then (self.hora_partida.seconds <
	 * self.hora_chegada.seconds) else (self.hora_partida.minutes <
	 * self.hora_chegada.minutes) endif else (self.hora_partida.hours <
	 * self.hora_chegada.hours) endif
	 * 
	 * inv numeroDeVoosChegadaSimultaneos (self.ocorre_em->collectNested($e :
	 * CalendarDate | $e.realiza_se)->flatten->select($elem10 : Voo |
	 * ($elem10.hora_chegada = $elem10.hora_chegada))->collectNested($e : Voo |
	 * $e.realizacao)->flatten->collectNested($e : Realizacao |
	 * $e.cancelada)->asSet->count(false) <= self.chegada.n_pistas)
	 * 
	 * inv vooMilhasMaiorOuIgualAZero (self.milhas > 0)
	 * 
	 * inv numeroDeVoosPartidaSimultaneos (self.ocorre_em->collectNested($e :
	 * CalendarDate | $e.realiza_se)->flatten->select($elem9 : Voo |
	 * ($elem9.hora_partida = self.hora_partida))->collectNested($e : Voo |
	 * $e.realizacao)->flatten->collectNested($e : Realizacao |
	 * $e.cancelada)->asSet->count(false) <= self.partida.n_pistas)
	 * 
	 * inv vooNumeroUnico Voo.allInstances->isUnique($elem8 : Voo | self.numero)
	 */
}
